package com.nbcio.modules.erp.export.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nbcio.modules.erp.export.domain.ErpExportJob;
import com.nbcio.modules.erp.export.mapper.ErpExportJobMapper;
import com.nbcio.modules.erp.export.service.IErpExportJobService;
import com.nbcio.modules.erp.material.domain.ErpMaterial;
import com.nbcio.modules.erp.material.excel.MaterialExportDTO;
import com.nbcio.modules.erp.material.service.IErpMaterialService;
import com.nbcio.modules.erp.warehouse.domain.ErpWarehouse;
import com.nbcio.modules.erp.warehouse.excel.WarehouseExportDTO;
import com.nbcio.modules.erp.warehouse.service.IErpWarehouseService;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class ErpExportJobServiceImpl extends ServiceImpl<ErpExportJobMapper, ErpExportJob> implements IErpExportJobService {

    @Resource private IErpMaterialService materialService;
    @Resource private IErpWarehouseService warehouseService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Long createJob(Long userId, String module, String paramsJson) {
        ErpExportJob job = new ErpExportJob();
        job.setJobId(Math.abs(new Random().nextLong()));
        job.setUserId(userId);
        job.setModule(module);
        job.setParamsJson(paramsJson);
        job.setStatus("PENDING");
        job.setProgress(0);
        job.setCreateTime(LocalDateTime.now());
        this.save(job);
        return job.getJobId();
    }

    @Override
    @Async
    public void runJobAsync(Long jobId) {
        ErpExportJob job = this.getById(jobId);
        if (job == null) return;
        try {
            job.setStatus("RUNNING");
            job.setProgress(5);
            this.updateById(job);

            Map<String, Object> params = objectMapper.readValue(job.getParamsJson(), Map.class);
            List<String> columns = (List<String>) params.get("columns");
            List<String> ids = (List<String>) params.get("ids");

            if ("material".equals(job.getModule())) {
                LambdaQueryWrapper<ErpMaterial> qw = buildMaterialWrapper(params);
                if (ids != null && !ids.isEmpty()) qw.in(ErpMaterial::getMaterialId, ids.stream().map(Long::parseLong).toList());
                List<ErpMaterial> list = materialService.list(qw);
                List<MaterialExportDTO> rows = list.stream().map(it -> {
                    MaterialExportDTO dto = new MaterialExportDTO();
                    dto.setMaterialCode(it.getMaterialCode());
                    dto.setMaterialName(it.getMaterialName());
                    dto.setUomId(it.getUomId() == null ? null : String.valueOf(it.getUomId()));
                    dto.setEnableBatchText(it.getEnableBatch() != null && it.getEnableBatch() == 1 ? "是" : "否");
                    dto.setEnableSerialText(it.getEnableSerial() != null && it.getEnableSerial() == 1 ? "是" : "否");
                    dto.setStatusText(it.getStatus() != null && it.getStatus() == 1 ? "启用" : "停用");
                    dto.setBarcode(it.getBarcode());
                    dto.setRemark(it.getRemark());
                    dto.setCreateTime(it.getCreateTime());
                    return dto;
                }).toList();
                String file = writeExcel("物料列表", MaterialExportDTO.class, rows, columns);
                job.setFilePath(file);
            } else if ("warehouse".equals(job.getModule())) {
                LambdaQueryWrapper<ErpWarehouse> qw = buildWarehouseWrapper(params);
                if (ids != null && !ids.isEmpty()) qw.in(ErpWarehouse::getWarehouseId, ids.stream().map(Long::parseLong).toList());
                List<ErpWarehouse> list = warehouseService.list(qw);
                List<WarehouseExportDTO> rows = list.stream().map(it -> {
                    WarehouseExportDTO dto = new WarehouseExportDTO();
                    dto.setWarehouseCode(it.getWarehouseCode());
                    dto.setWarehouseName(it.getWarehouseName());
                    dto.setOrgId(it.getOrgId() == null ? null : String.valueOf(it.getOrgId()));
                    dto.setEnableBinText(it.getEnableBin() != null && it.getEnableBin() == 1 ? "是" : "否");
                    dto.setEnableBatchText(it.getEnableBatch() != null && it.getEnableBatch() == 1 ? "是" : "否");
                    dto.setEnableSerialText(it.getEnableSerial() != null && it.getEnableSerial() == 1 ? "是" : "否");
                    dto.setStatusText(it.getStatus() != null && it.getStatus() == 1 ? "启用" : "停用");
                    dto.setRemark(it.getRemark());
                    dto.setCreateTime(it.getCreateTime());
                    return dto;
                }).toList();
                String file = writeExcel("仓库列表", WarehouseExportDTO.class, rows, columns);
                job.setFilePath(file);
            }
            job.setStatus("DONE");
            job.setProgress(100);
            this.updateById(job);
        } catch (Exception e) {
            job.setStatus("FAILED");
            job.setErrorMsg(e.getMessage());
            this.updateById(job);
        }
    }

    private LambdaQueryWrapper<ErpMaterial> buildMaterialWrapper(Map<String, Object> params) {
        LambdaQueryWrapper<ErpMaterial> qw = new LambdaQueryWrapper<>();
        ofString(params, "materialCode").ifPresent(v -> qw.like(ErpMaterial::getMaterialCode, v));
        ofString(params, "materialName").ifPresent(v -> qw.like(ErpMaterial::getMaterialName, v));
        ofInt(params, "status").ifPresent(v -> qw.eq(ErpMaterial::getStatus, v));
        ofInt(params, "enableBatch").ifPresent(v -> qw.eq(ErpMaterial::getEnableBatch, v));
        ofInt(params, "enableSerial").ifPresent(v -> qw.eq(ErpMaterial::getEnableSerial, v));
        ofString(params, "startTime"); ofString(params, "endTime");
        String st = (String) params.get("startTime"); String et = (String) params.get("endTime");
        if (st != null && et != null && !st.isEmpty() && !et.isEmpty()) qw.between(ErpMaterial::getCreateTime, st, et);
        return qw;
    }

    private LambdaQueryWrapper<ErpWarehouse> buildWarehouseWrapper(Map<String, Object> params) {
        LambdaQueryWrapper<ErpWarehouse> qw = new LambdaQueryWrapper<>();
        ofString(params, "warehouseCode").ifPresent(v -> qw.like(ErpWarehouse::getWarehouseCode, v));
        ofString(params, "warehouseName").ifPresent(v -> qw.like(ErpWarehouse::getWarehouseName, v));
        ofInt(params, "status").ifPresent(v -> qw.eq(ErpWarehouse::getStatus, v));
        ofInt(params, "enableBin").ifPresent(v -> qw.eq(ErpWarehouse::getEnableBin, v));
        ofInt(params, "enableBatch").ifPresent(v -> qw.eq(ErpWarehouse::getEnableBatch, v));
        ofInt(params, "enableSerial").ifPresent(v -> qw.eq(ErpWarehouse::getEnableSerial, v));
        String st = (String) params.get("startTime"); String et = (String) params.get("endTime");
        if (st != null && et != null && !st.isEmpty() && !et.isEmpty()) qw.between(ErpWarehouse::getCreateTime, st, et);
        return qw;
    }

    private <T> String writeExcel(String baseName, Class<T> clazz, List<T> rows, List<String> columns) throws Exception {
        String fileName = URLEncoder.encode(baseName + ".xlsx", StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
        File dir = new File("./export");
        if (!dir.exists()) dir.mkdirs();
        File out = new File(dir, System.currentTimeMillis() + "-" + fileName);
        var writer = EasyExcel.write(out, clazz);
        if (columns != null && !columns.isEmpty()) writer.includeColumnFieldNames(new HashSet<>(columns));
        writer.sheet(baseName).doWrite(rows);
        return out.getAbsolutePath();
    }

    private Optional<String> ofString(Map<String, Object> map, String key) {
        Object v = map.get(key);
        return v instanceof String && !((String) v).isEmpty() ? Optional.of((String) v) : Optional.empty();
    }
    private Optional<Integer> ofInt(Map<String, Object> map, String key) {
        Object v = map.get(key);
        if (v == null) return Optional.empty();
        if (v instanceof Number) return Optional.of(((Number) v).intValue());
        try { return Optional.of(Integer.parseInt(String.valueOf(v))); } catch (Exception e) { return Optional.empty(); }
    }
}


